package algo_lignes_sites.data.pre_trt.pre_trt_path.simplif_topo;

import algo_lignes_sites.data.junction.JunctionREF;
import algo_lignes_sites.data.path.A_Path;
import algo_lignes_sites.data.path.PathREF;
import algo_lignes_sites.params.Params_ls;
import commons.logs.Logger;
import commons.params_appli.ParamsAppli;
import commons.spatialindex.simplegrid.SimpleGridSPI;
import commons.utils.UtilsGeom;
import commons.utils.primitive_geom.Segment_bound;
import gnu.trove.set.hash.THashSet;
import java.util.ArrayList;
import java.util.HashMap;


public class SimplifTopo {

	// ZST = Zone de Simplification Topologique
	
	static Params_ls params;
	static ArrayList<PathREF> vPathREF;
	static ArrayList<PathREF> vPathREF_modified;

	// Attention, ce vecteur ne sera pas nettoyé durant les realisations de pattern
	// il n'y aura que l'ajout des nouvelles junction dedans
	// un gros ménage étant réalisé en fin de traitement de simplification
	// Seuls le spi de point multiple et le spi de path est tout le temps mis a jour apres chaque realisation de pattern
	static ArrayList<JunctionREF> vJunctionREF;

	// Ce vecteur n'est que pour de la lecture, il ne sera pas modifié
	static ArrayList<JunctionREF> vNodeREF;
	// Spi en lecture seule, jamais modifié
	static SimpleGridSPI spiNode;

	static SimpleGridSPI spiPath;

	// stocke temporairement uniquement les points multiples
	// durant le traitement, à chaque réalisation de pattern, ce vecteur et le spi associé sont mis à jour
	static ArrayList<JunctionREF> vPtMultiple;
	static SimpleGridSPI spiPtMultiple;


	// Variables MAJ par chaque pattern
	public static int nbPatternRealise_BoucleDansNode;

	public static int nbPatternRealise_PetitPath;

	public static int nbPatternRealise_3PtMultTo1;
	public static int nbPatternTeste_3PtMultTo1;
	
	public static int nbPatternRealise_2PtMultTo1;
	public static int nbPatternTeste_2PtMultTo1;
	
		
	static public int getNbPatternRealise() {return nbPatternRealise_BoucleDansNode + nbPatternRealise_PetitPath + nbPatternRealise_3PtMultTo1 + nbPatternRealise_2PtMultTo1;}
	static public int getNbPatternTeste() {return nbPatternTeste_3PtMultTo1 + nbPatternTeste_2PtMultTo1;}



	// vPathREF_modified contiendra tous les path modifies par cet algo
	//  en effet, ils devront etre re-simplifies (douglas peucker) apres car on va
	//  creer plein de nouveaux points pour faciliter certaines simplif topo 9et eviter des superpositions de segments)
	static public int simplifieTopo(Params_ls params, ArrayList<PathREF> vPathREF, ArrayList<JunctionREF> vNodeREF, ArrayList<JunctionREF> vJunctionREF, ArrayList<PathREF> vPathREF_modified,
			                           SimpleGridSPI spiPath, SimpleGridSPI spiNode, boolean bKeepLastStat) {
		
		
		Logger.log_INFO1("-- Simplification topologique du réseau --");
		
		if (params.factor_SimplifTopo <= 0) {
			Logger.log_INFO2("  -> rien à simplifier : rayon de simplification <= 0");
			return 0;
		}
		Logger.log_INFO2(" -> rayon de simplification = " + params.factor_SimplifTopo + " x largeur lignes");
		
		SimplifTopo.params = params;
		SimplifTopo.vPathREF = vPathREF;
		SimplifTopo.vPathREF_modified = vPathREF_modified;
		SimplifTopo.vJunctionREF = vJunctionREF;
		SimplifTopo.vNodeREF = vNodeREF;
		SimplifTopo.spiPath = spiPath;

		// Spi en lecture seule, jamais modifié
		SimplifTopo.spiNode = spiNode;
		spiNode.clear();
		spiNode.addObjects(vNodeREF);

		// Calcul de complexité de zone (en fait, on aura besoin de la valeur de complexité que sur les points multiples)
		// Pour l'instant, fonction non prise en charge : on simplifie tout de la même façon, sans critère aprticulier
//		spiJunction.clear();
//		spiJunction.addObjects(vJunctionREF);
//		CalculComplexiteZone.computeComplexiteForJunction(params, vPathREF, vJunctionREF, spiJunction);

		// Comme on ne calcule pas de complexité, il faut au moins faire ce pré-calcul : imposer à chaque
		//  objet l'échelle finale de traitement pour avoir sa vraie taille finale
		for (PathREF pathREF : vPathREF)
			pathREF.majContrainteGeom_echelleCouranteChanged(1/params.echelleCible);
		for (JunctionREF jREF : vJunctionREF)
			jREF.majContrainteGeom_echelleCouranteChanged(1/params.echelleCible);


		// Extrait de junction : on ne va travailler dans la suite que sur les points multiple
		//reinitDataPtMultiple(); on init ca au debut des Traitement de chaque Pattern en fait

		if (!bKeepLastStat) {
			nbPatternRealise_BoucleDansNode = 0;
			nbPatternRealise_PetitPath = 0;
			nbPatternRealise_3PtMultTo1 = 0;
			nbPatternTeste_3PtMultTo1 = 0;
			nbPatternRealise_2PtMultTo1 = 0;
			nbPatternTeste_2PtMultTo1 = 0;
		}

		// Lecture dans le fichier INI sur les tivé ou non
		boolean bUsePattern_3_1 = ParamsAppli.getParam_b("LigneSite.SIMPLIF_TOPO_3_1");
		boolean bUsePattern_2_1 = ParamsAppli.getParam_b("LigneSite.SIMPLIF_TOPO_2_1");
		boolean bUsePattern_BoucleNode = ParamsAppli.getParam_b("LigneSite.SIMPLIF_TOPO_BOUCLE_NODE");
		boolean bUsePattern_PetitPath = ParamsAppli.getParam_b("LigneSite.SIMPLIF_TOPO_PETIT_PATH");

		Logger.log_INFO2(" -> Pattern utilisés :");
		Logger.log_INFO2("      3->1        : " + (bUsePattern_3_1 ? "OUI" : "NON"));
		Logger.log_INFO2("      2->1        : " + (bUsePattern_2_1 ? "OUI" : "NON"));
		Logger.log_INFO2("      Boucle Node : " + (bUsePattern_BoucleNode ? "OUI" : "NON"));
		Logger.log_INFO2("      Petit Path  : " + (bUsePattern_PetitPath ? "OUI" : "NON"));
		
		boolean bReBoucle = true;
		while (bReBoucle) {
			bReBoucle = false;

			if (bUsePattern_BoucleNode && Pattern_BoucleDansNode.traite()) bReBoucle = true;
			if (bUsePattern_PetitPath && Pattern_PetitPath.traite()) bReBoucle = true; // marche mieux si a la fin ?
			if (bUsePattern_3_1 && Pattern_nPtMultTo1.traite(Pattern_nPtMultTo1.PATTERN_3_1)) bReBoucle = true;
			if (bUsePattern_2_1 && Pattern_nPtMultTo1.traite(Pattern_nPtMultTo1.PATTERN_2_1)) bReBoucle = true;
			
		}

		// En fin de traitement, on doit reconstituer le vecteur global de junction, qui n'a jamais été nettoyé
		// En revanche, on y a bien ajouté l'ensemble des nouveaux points créés lors de la réalisation des pattern
		JunctionREF.cleanVJunctionREFGlobal(SimplifTopo.vPathREF, SimplifTopo.vJunctionREF);

		clear();
		
		return getNbPatternRealise();
	}

	// Détruit les objets éventuellement créés par ce module et supprime les liens statiques avec les objets passés en paramètres
	private static void clear() {
		vPathREF = null;
		vPathREF_modified = null;
		vJunctionREF = null;
		vNodeREF = null;
		spiPath = null;
		spiNode = null;
		if (spiPtMultiple!=null)
			spiPtMultiple.clear();
		spiPtMultiple = null;
		if (vPtMultiple!=null)
			vPtMultiple.clear();
		vPtMultiple=null;
	}

	protected static void reinitDataPtMultiple() {
		if (vPtMultiple==null || spiPtMultiple==null) {
			// 1ere initialisation
			vPtMultiple = new ArrayList<JunctionREF>();
			for (JunctionREF j : vJunctionREF) {
				if (j.isIntersectOrConnect3())
					vPtMultiple.add(j);
			}
			spiPtMultiple = new SimpleGridSPI("Spi pt multiple", spiPath.getRectEmprise(), SimpleGridSPI.getTailleFor(vPtMultiple.size()));
			spiPtMultiple.addObjects(vPtMultiple);

		} else {
			// Reactualisation
			
			THashSet<JunctionREF> set = new THashSet<JunctionREF>(vPtMultiple.size());
			// On doit aller chercher les pt multiple dans les path, car vJunctionREF n'est peut etre pas a jour
			//  (il n'est mis à jour qu'en fin de simplif topo)
			// Par contre, le vecteur de path est mis a jour à chaque realisation de Pattern
			for (PathREF p : vPathREF) {
				ArrayList<JunctionREF> vJ = p.getVJunctionREF();
				int nbJ = vJ.size();
				for (int i=0 ; i<nbJ ; i++) {
					JunctionREF j = vJ.get(i);
					if (j.isIntersectOrConnect3())
						set.add(j);
				}
			}
			vPtMultiple.clear();
			vPtMultiple.addAll(set);
			spiPtMultiple.clear();
			spiPtMultiple.addObjects(vPtMultiple);
		}
	}
	
	// Chaque nouveau segment ajouté ne doit créer aucune intersection avec les path proches
	//  (pour lesquels on ne considere pas l'eventuelle portions effacées)
	static boolean isThereNewIntersection(ArrayList<PathREF> vPathProche, ArrayList<Segment_bound> vSegmentAdded, HashMap<PathREF,PortionPath> mapPortionDeleted) {
		
		//********************************************************************************
		// Tout d'abord les nouveaux segment entre eux : ils ne peuvent en general pas s'intersecter
		// mais ils pourraient être superposés
		int nbSegAdded = vSegmentAdded.size();
		for (int i=0; i<nbSegAdded; i++) {
			Segment_bound seg1 = vSegmentAdded.get(i);
			
			for (int k=i+1; k<nbSegAdded; k++) {
				Segment_bound seg2 = vSegmentAdded.get(k);
				if (!seg1.boundingBoxIntersect(seg2))
					continue;
				if (seg1.equalsCoord(seg2) || UtilsGeom.areSegmentInternallyIntersected(seg1, seg2) || UtilsGeom.areSegmentSuperposed(seg1, seg2)) {
					Logger.log_DEBUG2(" -> Nouveaux segmemts superposes");
					return true;
				}
			}
		}
		
		//********************************************************************************
		// Puis, aucun des nouveaux segments ne doit creer d'intersection avec les path proche
		int nbPathProche = vPathProche.size();
		// TODO_ABANDONNE v17 OPTIM : interchanger boucle path et segment
			
		for (int k=0; k<nbPathProche; k++) {
			PathREF path = vPathProche.get(k);
			PortionPath portionDeleted = mapPortionDeleted.get(path); // peut etre null
			if (portionDeleted!=null && portionDeleted.pathToBeSuppressed())
				continue; // le path va etre supprime ...

			int nbJ = path.vJunctionREF.size();
			for (int x=0; x<nbJ-1; x++) {
				// test si le segment courant du path courant n'est pas dans une portion supprimée
				if (portionDeleted != null && x>=portionDeleted.index1 && x<portionDeleted.index2)
					continue;
				JunctionREF j1 = path.vJunctionREF.get(x);
				JunctionREF j2 = path.vJunctionREF.get(x+1);
				
				// bounding box du segment courant du path
				double segpath_xMin, segpath_xMax, segpath_yMin, segpath_yMax;
				if (j1.x < j2.x) {
					segpath_xMin = j1.x;
					segpath_xMax = j2.x;
				} else {
					segpath_xMin = j2.x;
					segpath_xMax = j1.x;
				}
				if (j1.y < j2.y) {
					segpath_yMin = j1.y;
					segpath_yMax = j2.y;
				} else {
					segpath_yMin = j2.y;
					segpath_yMax = j1.y;
				}
					
				for (int i=0; i<nbSegAdded; i++) {
					Segment_bound seg_added = vSegmentAdded.get(i);

					if (!seg_added.boundingBoxIntersect(segpath_xMin, segpath_xMax, segpath_yMin, segpath_yMax))
						continue;
					
					if (UtilsGeom.areSegmentInternallyIntersected(seg_added.pt1, seg_added.pt2, j1, j2) ||
							UtilsGeom.areSegmentSuperposed(seg_added.pt1, seg_added.pt2, j1, j2)) {
						Logger.log_DEBUG2(" -> Nouvelle intersection avec autres path");
						return true;
					}
				}
			}
		}
		
		// Arrivé la, il n'y a pas de nouvelle intersection
		return false;
	}




	//**********************************************************************************
	
	// Renvoie les junctions situées dans le rayon de simplification topo de jCenter
//	static ArrayList<JunctionREF> getJunctionInZST(JunctionREF jCenter) {
//		double largeurSimplif = jCenter.getLargestPathContenant()*params.factor_SimplifTopo;
//		ArrayList vRes = spiJunction.getPointsAtDist(jCenter, largeurSimplif/2, true);
//		assert (vRes.size() >= 1); // il doit au moins y avoir le jCenter dans le résultat
//		return vRes;
//	}


	// Renvoie les junctions de type INTERSECT ou CONNECT_3 dans le rayon de simplification topo de jCenter
	// Le resultat contiendra a minima jCenter
	static ArrayList<JunctionREF> getPtMultiple_inZST (JunctionREF jCenter) {

		double largeurSimplif = jCenter.getLargestPathContenant_withDissy()*params.factor_SimplifTopo;
		ArrayList vJinZST = spiPtMultiple.getPointsAtDist(jCenter, largeurSimplif, true);
		int nbJ = vJinZST.size();
		assert (nbJ >= 1); // il doit au moins y avoir le jCenter dans le résultat
		return vJinZST;
	}
	
	// Renvoie les junction de type INTERSECT dans la ZST, null si aucune intersect
	static ArrayList<JunctionREF> getIntersect_inZST (ArrayList<JunctionREF> vJ_ZST) {
		ArrayList<JunctionREF> vIntersect = null;
		int nbJ = vJ_ZST.size();
		for (int i=0; i<nbJ; i++)
			if (vJ_ZST.get(i).isIntersect()) {
				if (vIntersect==null) vIntersect = new ArrayList<JunctionREF>();
				vIntersect.add(vJ_ZST.get(i));
			}
		return vIntersect;
	}



	//************************************************************************************************
	//************************************************************************************************
	//************************************************************************************************


	// représente une portion de path, entre 2 points du path
	public static class PortionPath {
		public A_Path path;
		public int index1;
		public int index2;
		public boolean isIndex1ADeletedJunction;
		public boolean isIndex2ADeletedJunction;

		public PortionPath(A_Path path, int index1, int index2, boolean isIndex1ADeletedJunction, boolean isIndex2ADeletedJunction) {
			this.path=path;
			this.index1=index1;
			this.index2=index2;
			this.isIndex1ADeletedJunction = isIndex1ADeletedJunction;
			this.isIndex2ADeletedJunction = isIndex2ADeletedJunction;
		}
		
		// si la portion représente tout le path, on devra le supprimer !
		public boolean pathToBeSuppressed() {
			return isIndex1ADeletedJunction && isIndex2ADeletedJunction;
		}
	}
	
}
